package ygg.encoding.swf.tag.codec
{
	import j.sus2maj7.encoding.*;
	import j.sus2maj7.context.*;
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import ygg.encoding.swf.tag.*;
	import ygg.encoding.swf.tag.object.*;
	
	import ygg.encoding.swf.abc.*;
	import ygg.encoding.swf.abc.structure.*;
	import ygg.encoding.swf.abc.constant.*;
	
	
	
	public class DoABCCodec extends TagCodec
	{
		//*
		//public var major:int
		//public var minor:int
		
		
		//public var defaults:Array = new Array(Constants.constantKinds.length)

		
		public const publicNs:Namespace = new Namespace("")
		public const anyNs:Namespace = new Namespace("*")
		
		//public var magic:int
		
		//added
		//protected var metadata:Array;
		//*/
		
		public function DoABCCodec()
		{
			super();
		}
		
		override public function decode(binary:IBinary, context:IContext=null):*
		{
			var abc:DoABC = new DoABC();
			var b:TagBinary = binary as TagBinary;
			b.position = 0;
			b.syncBits();
			
			trace('doabccode');
			
			//look for magic
			var magic:int = b.readInt();
			trace("magic " + magic.toString(16) + "\n");
			if (magic != (46 << 16 | 14) && magic != (46 << 16 | 15) && magic != (46 << 16 | 16))
			{
				throw new Error("not an abc file.  magic=" + magic.toString(16));
			}
			
			var ctx:AbcContext = new AbcContext();
			ctx.constantPool = decodeConstantPool(b);
			
			
			
			ctx.methods = decodeMethodInfos(b, ctx);
			
			
			ctx.metadata = decodeMetadataInfos(b, ctx);
			ctx.instances = decodeInstanceInfos(b, ctx);
			ctx.classes = decodeClassInfos(b, ctx);
			ctx.scripts = decodeScriptInfos(b, ctx);
			//ctx.methodBodies =
			decodeMethodBodies(b, ctx);
			
			
			
			
			//abc.scripts = ctx.constantPool.scripts;
			//abc.methods = ctx.constantPool.methods;
			abc.context = ctx;
			
			trace('---');
			return abc;
		}
		
		protected function decodeConstantPool(b:TagBinary):ConstantPool
		{
			var i:int, j:int;
			var n:int;
			var kind:int;

			var ints:Array;
			var uints:Array;
			var doubles:Array;
			var strings:Array;
			var namespaces:Array;
			var nsSets:Array;
			var multinames:Array;
			//var start:int = b.position;
			
			// ints
			n = b.readEncU32();
			ints = [0];
			
			for (i = 1; i < n; i++)
			{
				ints[i] = b.readEncU32();
			}
				
			// uints
			n = b.readEncU32();
			uints = [0];
			
			for (i = 1; i < n; i++)
			{
				uints[i] = uint(b.readEncU32());
			}
				
			// doubles
			n = b.readEncU32();
			doubles = [NaN];
			
			for (i = 1; i < n; i++)
			{
				doubles[i] = b.readDouble();
			}

			// strings
			n = b.readEncU32();
			strings = [""];
			
			for (i = 1; i < n; i++)
			{
				strings[i] = b.readUTFBytes( b.readEncU32() );
			}

			// namespaces
			n = b.readEncU32();
			namespaces = [publicNs]; //JF should this be anyNs?
			
			for (i = 1; i < n; i++)
			{
				switch (b.readByte())
				{
					case NamespaceKind.NAMESPACE_:
					case NamespaceKind.PACKAGE_NAMESPACE:
					case NamespaceKind.PACKAGE_INTERNAL_NS:
					case NamespaceKind.PROTECTED_NAMESPACE:
					case NamespaceKind.EXPLICIT_NAMESPACE:
					case NamespaceKind.STATIC_PROTECTED_NS:
						{
							namespaces[i] = new Namespace(strings[b.readEncU32()]);
							// todo mark kind of namespace.
							break;
						}
					case NamespaceKind.PRIVATE_NS:
						b.readEncU32(); //JF: why not saved?
						namespaces[i] = new Namespace(null, "private");
						break;
				}
			}
			
			// namespace sets
			n = b.readEncU32();
			nsSets = [null];
			
			for (i=1; i < n; i++)
			{
				var count:int = b.readEncU32();
				var nsset:Array = nsSets[i] = [];
				
				for (j = 0; j < count; j++)
				{
					nsset[j] = namespaces[b.readEncU32()];
				}
			}

			// multinames
			n = b.readEncU32();
			multinames = [null];
			namespaces[0] = anyNs; //JF: i dont understand switching the default
			strings[0] = "*"; // any name
			
			for (i = 1; i < n; i++)
			{
				switch (b.readByte())
				{
					case MultinameKind.QNAME:
					case MultinameKind.QNAME_A:
						multinames[i] = new QName(namespaces[b.readEncU32()], strings[b.readEncU32()])
						break;
					
					case MultinameKind.RTQNAME:
					case MultinameKind.RTQNAME_A:
						multinames[i] = new QName(strings[b.readEncU32()])
						break;
					
					case MultinameKind.RTQNAME_L:
					case MultinameKind.RTQNAME_LA:
						multinames[i] = null
						break;
					//not in spec
					case MultinameKind.NAME_L:
					case MultinameKind.NAME_LA:
						multinames[i] = new QName(new Namespace(""), null)
						break;
					
					case MultinameKind.MULTINAME:
					case MultinameKind.MULTINAME_A:
						var mn:Multiname = new Multiname();
						mn.name = strings[b.readEncU32()]
						mn.nsset = nsSets[b.readEncU32()];
						
						multinames[i] = mn;
						break;

					case MultinameKind.MULTINAME_L:
					case MultinameKind.MULTINAME_LA:
						var mn2:Multiname = new Multiname();
						mn2.nsset = nsSets[b.readEncU32()];
						mn2.name = null;
						multinames[i] = mn2;
						break;
						
					default:
						throw new Error("invalid kind " + b[b.position-1])
				}
			}
			
			namespaces[0] = publicNs;
			strings[0] = "*";
			
			var cp:ConstantPool = new ConstantPool();
			cp.ints = ints;
			cp.uints = uints;
			cp.doubles = doubles;
			cp.strings = strings;
			cp.namespaces = namespaces;
			cp.nsSets = nsSets;
			cp.multinames = multinames;
			
			return cp;
		}
		
		protected function decodeMethodInfos(b:TagBinary, ctx:AbcContext):Vector.<MethodInfo>
		{
			//var start:int = b.position;
			
			var multinames:Array = ctx.constantPool.multinames;
			var strings:Array = ctx.constantPool.strings;
			
			multinames[0] = new QName(publicNs, "*");
			
			var numMethods:int = b.readEncU32();
			var methods:Vector.<MethodInfo> = new Vector.<MethodInfo>();
			
			for (var i:int=0; i < numMethods; i++)
			{
				var mi:MethodInfo = new MethodInfo();
				methods[i] = mi;
				
				var numParams:int = b.readEncU32();
				mi.returnType = multinames[b.readEncU32()];
				mi.paramTypes = [];
				
				for (var j:int = 0; j < numParams; j++)
				{
					mi.paramTypes[j] = multinames[b.readEncU32()]; //0=*type
				}
				
				mi.debugName = strings[b.readEncU32()]; //why debug name and not name?
				mi.flags = b.readByte();
				
				if (mi.flags & MethodFlag.HAS_OPTIONAL)
				{
					// has_optional
					var numOptions:int = b.readEncU32();
					mi.optionalValues = [];
					
					for( var k:int = numParams-numOptions; k < numParams; ++k)
					{
						var index:int = b.readEncU32()    // optional value index
						var kind:int = b.readByte() // kind byte for each default value
						/*
						if (index == 0)
						{
							// kind is ignored, default value is based on type
							mi.optionalValues[k] = undefined
						}
						else
						{
							if (ctx.constantPool.getPool(kind))
							{*/
								mi.optionalValues[k] = ctx.constantPool.getPool(kind)[index];
						/*	}
                            else
							{
							    trace("ERROR kind=" + kind + " method_id " + i + "\n")
							}
						}//*/
					}
				}
				
				if (mi.flags & MethodFlag.HAS_PARAM_NAMES)
				{
					// has_paramnames
					mi.paramNames = [];
					
					for( var l:int = 0; l < numParams; ++l)
                    {
                        mi.paramNames.push(strings[ b.readEncU32() ]);
                    }
                }
			}
			
			return methods;
			//trace("MethodInfo count " +numMethods+ " size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+" %\n")
		}

		protected function decodeMetadataInfos(b:TagBinary, ctx:AbcContext):Vector.<MetadataInfo>
		{
			var strings:Array = ctx.constantPool.strings;
			
			var count:int = b.readEncU32();
			var metadata:Vector.<MetadataInfo> = new Vector.<MetadataInfo>();
			
			for (var i:int=0; i < count; i++)
	        {
				// MetadataInfo
				var mi:MetadataInfo = new MetadataInfo();
				metadata[i] = mi;
				
				mi.name = strings[b.readEncU32()];
				
	            var numItems:int = b.readEncU32();
	            var itemNames:Array = []
				
	            for (var q:int = 0; q < numItems; ++q)
				{
					itemNames[q] = strings[b.readEncU32()]; // name
				}
				
				mi.items = [];
				for (var r:int = 0; r < numItems; ++r)
				{
					mi.items[itemNames[r]] = strings[b.readEncU32()]; // value
				}
			}
			
			return metadata;
		}

		protected function decodeInstanceInfos(b:TagBinary, ctx:AbcContext):Vector.<TraitInfo>
		{trace('pii');
			var multinames:Array = ctx.constantPool.multinames;
			var namespaces:Array = ctx.constantPool.namespaces;
			
			var methods:Vector.<MethodInfo> = ctx.methods;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			var instances:Vector.<TraitInfo> = new Vector.<TraitInfo>();
			
			for (var i:int=0; i < count; i++)
	        {
	        	var t:TraitInfo = new TraitInfo();
				instances[i] = t;
				
	        	t.name = multinames[b.readEncU32()];
	        	t.base = multinames[b.readEncU32()]; //super name
	        	t.flags = b.readByte();
				
				if (t.flags & InstanceFlag.CLASS_PROTECTED_NS_)
				{
					t.protectedNs = namespaces[b.readEncU32()];
				}
	
				var numInterfaces:int = b.readEncU32();
	
				for (var j:int = 0; j < numInterfaces; j++)
				{
	        		t.interfaces[i] = multinames[b.readEncU32()];
				}
				
	        	var mi:MethodInfo = methods[b.readEncU32()];
				t.init = mi;
				
	        	mi.name = t.name; //constructor
	        	mi.kind = TraitKind.METHOD;
	        	mi.id = -1; //not sure
				
				trace(t.name);
	        	decodeTraitInfos(t, b, ctx );
				trace();
	        }
			
			return instances;
			//trace("InstanceInfo size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+" %\n")
		}
		
		public function decodeTraitInfos(t:TraitInfo, b:TagBinary, ctx:AbcContext):void
		{
			//trace('a');
			//needs multinames metadata methods classes
			var multinames:Array = ctx.constantPool.multinames;
			var methods:Vector.<MethodInfo> = ctx.methods;
			var metadata:Vector.<MetadataInfo> = ctx.metadata;
			var classes:Vector.<TraitInfo> = ctx.classes;
			
			var namecount:int = b.readEncU32();
			//trace('b');
			for (var i:int=0; i < namecount; i++)
			{
				var name:* = multinames[b.readEncU32()];
				var tag:int = b.readByte();
				var kind:int = tag & 0xf;
				var member:MemberInfo;
				trace(kind, name);
				switch(kind) //JRF:
				{
					case TraitKind.SLOT:
					case TraitKind.CONST:
					case TraitKind.CLASS:
					
						member = new SlotInfo();
						var slot:SlotInfo = SlotInfo(member);
						slot.id = b.readEncU32();
						if (slot.id == 0)
						{
							//trace("SLOT ID 0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!");trace(t.slots.length);
							t.slots[t.slots.length] = slot;
						}
						else
						{
							//trace("SLOT ID ok", slot.id);
							if (t.slots[slot.id] != null) trace('SLOT ALREADY TAKEN', t.slots[slot.id]);
							t.slots[slot.id] = slot;
						}
						
						
						if (kind==TraitKind.SLOT || kind==TraitKind.CONST) //trait_slot
						{//trace('d');
							slot.type = multinames[b.readEncU32()];
							var index:int = b.readEncU32();
							if (index)
							{//trace('e');
								slot.value = ctx.constantPool.getPool(b.readByte())[index]
							}
						}
						
						else // (kind == TraitKind.TRAIT_Class) //trait_class
						{//trace('f');
							slot.value = classes[b.readEncU32()]
						}
						break;
					case TraitKind.FUNCTION:
						member = new SlotMethodInfo();
						member = methods[b.readEncU32()];
						var smf:SlotMethodInfo = SlotMethodInfo(member);
						smf.id = b.readEncU32();
						if (smf.id == 0) trace("SLOTf ID 0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
						t.slots[smf.id] = smf;
					case TraitKind.METHOD:
					case TraitKind.GETTER:
					case TraitKind.SETTER:
					
						var disp_id:int = b.readEncU32();
						member = methods[b.readEncU32()];
						
						var method:MethodInfo = MethodInfo(member);
						t.methods[disp_id] = method; //what if index taken?
						method.id = disp_id
						
						//trace.print("\t",TraitKind.traitKinds[kind],name,disp_id,method,"// disp_id", disp_id)
						break;
					
						
					default:
						//throw new Error("invalid trait kind", kind);
						break;
				}
				
				if (!member)
				{
					trace("error trait kind " + kind + "\n");
				}
				
				member.kind = kind;
				member.name = name;
				
				t.members[i] = member;
				t.names[String(name)] = t.members[i];
				
	            if ( (tag >> 4) & TraitAttribute.METADATA )
				{//trace('attr');
					member.metadata = [];
					
					for (var j:int = 0, mdCount:int = b.readEncU32(); j < mdCount; ++j)
					{
						member.metadata[j] = metadata[b.readEncU32()];
					}
				}
			}
			
		}

		protected function decodeClassInfos(b:TagBinary, ctx:AbcContext):Vector.<TraitInfo>
		{
			trace('pci');
			//needs instances and methods
			var instances:Vector.<TraitInfo> = ctx.instances;
			var methods:Vector.<MethodInfo> = ctx.methods;
			
			//var start:int = b.position;
			var count:int = instances.length;
			var classes:Vector.<TraitInfo> = new Vector.<TraitInfo>();// ctx.constantPool.classes = [];
			
			for (var i:int=0; i < count; i++)
	        {
	        	var t:TraitInfo = new TraitInfo();
				classes[i] = t;
	        	t.init = methods[b.readEncU32()];
	        	t.base = "Class";
	        	t.itraits = instances[i];
	        	t.name = t.itraits.name + "$";
	        	t.init.name = t.itraits.name + "$cinit";
	        	t.init.kind = TraitKind.METHOD;
				trace(t.name);
	        	decodeTraitInfos(t, b, ctx )
				trace();
			}
			
			return classes;
			//trace("ClassInfo size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+"%\n")
		}

		protected function decodeScriptInfos(b:TagBinary, ctx:AbcContext):Vector.<TraitInfo>
		{trace('psi');
			//needs multinames methods
			var multinames:Array = ctx.constantPool.multinames;
			var methods:Vector.<MethodInfo> = ctx.methods;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			
			var scripts:Vector.<TraitInfo> = new Vector.<TraitInfo>();
			//scripts = ctx.constantPool.scripts = [];
			
			for (var i:int=0; i < count; i++)
	        {
	        	var t:TraitInfo = new TraitInfo();
				
				scripts[i] = t;
				
	        	t.name = "script_" + i;
	        	t.base = multinames[0]; // Object
	        	t.init = methods[b.readEncU32()];
	        	t.init.name = t.name + "$init";
				t.init.kind = TraitKind.METHOD;
				
				trace(t.name);
	        	decodeTraitInfos(t, b, ctx );
				trace('');
	        }
			
			return scripts;
			//trace("ScriptInfo size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+" %\n")
		}

		protected function decodeMethodBodies(b:TagBinary, ctx:AbcContext):Vector.<MethodBodyInfo>
		{trace('pmb');
			//needs methods multinames
			var methods:Vector.<MethodInfo> = ctx.methods;
			var multinames:Array = ctx.constantPool.multinames;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			
			var methodBodies:Vector.<MethodBodyInfo> = new Vector.<MethodBodyInfo>();
			
			for (var i:int=0; i < count; i++)
	        {
	        	var mi:MethodInfo = methods[b.readEncU32()];
				
				var mb:MethodBodyInfo = new MethodBodyInfo();
				methodBodies[i] = mb;
				
				//mb.method = methods[b.readEncU32()];
				//mb.method.methodBody = mb;
				mi.methodBody = mb;
				
				mb.maxStack = b.readEncU32();
	        	mb.numLocals = b.readEncU32();
				
	        	mb.initScopeDepth = b.readEncU32();
	        	mb.maxScopeDepth = b.readEncU32();
				
	        	mb.localScopeSize = mb.maxScopeDepth - mb.initScopeDepth;
	
				var codeLength:int = b.readEncU32();
	
				var code:ByteArray = new ByteArray();
				code.endian = Endian.LITTLE_ENDIAN;
				
	        	if (codeLength > 0)
				{
		        	b.readBytes(code, 0, codeLength);
					mb.binary = new AbcBinary(code);
					
				}
				
	       		var numExceptions:int = b.readEncU32();
				mb.exceptions = new Vector.<ExceptionInfo>();
				
	       		for (var j:int = 0; j < numExceptions; j++)
	       		{
					var exc:ExceptionInfo = new ExceptionInfo();
					
	       			exc.from = b.readEncU32();
	       			exc.to = b.readEncU32();
	       			exc.target = b.readEncU32();
	       			exc.type = multinames[b.readEncU32()];  //JRF:spec says strings!
					//trace("magic " + magic.toString(16))
					//if (magic >= (46<<16|16))
					exc.name = multinames[b.readEncU32()];
					
					
					
					mb.exceptions[j] = exc;
	       		}
				
				mb.activation = new TraitInfo(); //JRF: why activation and not traits
				
				trace('mb');
	       		decodeTraitInfos(mb.activation, b, ctx);
				trace();
	        }
			
			return methodBodies;
		}
		
		
		
		
		override public function encode(o:*, context:IContext=null):IBinary
		{
			var b:TagBinary = new TagBinary();
			var obj:DoABC = o as DoABC;
			
			
			
			return b;
		}
	}
}